Skip to content

Shiro 反序列化漏洞

获取漏洞相关代码

  1. 拉取shiro代码

    https://github.com/apache/shiro

  2. 切换到1.0.x分支

  3. 查看org.apache.shiro.web.mgt.CookieRememberMeManager继承的org.apache.shiro.mgt.AbstractRememberMeManager类

代码分析

org.apache.shiro.mgt.AbstractRememberMeManager类第80行硬编码了默认的加解密密钥

java
/**
     * The following Base64 string was generated by auto-generating an AES Key:
     * <pre>
     * AesCipherService aes = new AesCipherService();
     * byte[] key = aes.generateNewKey().getEncoded();
     * String base64 = Base64.encodeToString(key);
     * </pre>
     * The value of 'base64' was copied-n-pasted here:
     */
    private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");

搜索“DEFAULT_CIPHER_KEY_BYTES”的调用点,找到一处调用点,在该类的构造方法中。

java
/**
     * Default constructor that initializes a {@link DefaultSerializer} as the {@link #getSerializer() serializer} and
     * an {@link AesCipherService} as the {@link #getCipherService() cipherService}.
     */
    public AbstractRememberMeManager() {
        this.serializer = new DefaultSerializer<PrincipalCollection>();
        this.cipherService = new AesCipherService();
        setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
    }

setCipherKey方法中,将传入的DEFAULT_CIPHER_KEY_BYTES设置为encryptionCipherKey和decryptionCipherKey。

查看org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals方法。

java
/**
     * Implements the interface method by first {@link #getRememberedSerializedIdentity(SubjectContext) acquiring}
     * the remembered serialized byte array.  Then it {@link #convertBytesToPrincipals(byte[], SubjectContext) converts}
     * them and returns the re-constituted {@link PrincipalCollection}.  If no remembered principals could be
     * obtained, {@code null} is returned.
     * <p/>
     * If any exceptions are thrown, the {@link #onRememberedPrincipalFailure(RuntimeException, SubjectContext)} method
     * is called to allow any necessary post-processing (such as immediately removing any previously remembered
     * values for safety).
     *
     * @param subjectContext the contextual data, usually provided by a {@link Subject.Builder} implementation, that
     *                       is being used to construct a {@link Subject} instance.
     * @return the remembered principals or {@code null} if none could be acquired.
     */
    public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
        PrincipalCollection principals = null;
        try {
            byte[] bytes = getRememberedSerializedIdentity(subjectContext);
            //SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
            if (bytes != null && bytes.length > 0) {
                principals = convertBytesToPrincipals(bytes, subjectContext);
            }
        } catch (RuntimeException re) {
            principals = onRememberedPrincipalFailure(re, subjectContext);
        }
 
        return principals;
    }

其中getRememberedSerializedIdentity(subjectContext)方法调用的是org.apache.shiro.web.mgt.CookieRememberMeManager#getRememberedSerializedIdentity,其中值得注意的是,该方法在进行Base64反编码时,先将Base64字符串的长度对4取模,如果字符串长度不是4的倍数,则补齐为4的倍数,补齐字符为等号。

java
/**
     * Sometimes a user agent will send the rememberMe cookie value without padding,
     * most likely because {@code =} is a separator in the cookie header.
     * <p/>
     * Contributed by Luis Arias.  Thanks Luis!
     *
     * @param base64 the base64 encoded String that may need to be padded
     * @return the base64 String padded if necessary.
     */
    private String ensurePadding(String base64) {
        int length = base64.length();
        if (length % 4 != 0) {
            StringBuilder sb = new StringBuilder(base64);
            for (int i = 0; i < length % 4; ++i) {
                sb.append('=');
            }
            base64 = sb.toString();
        }
        return base64;
    }

回到org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals方法,base64反编码后的字符串数组通过org.apache.shiro.mgt.AbstractRememberMeManager#convertBytesToPrincipals方法进行解密和反序列化。

参考文档

Shiro rememberMe反序列化攻击检测思路